﻿using System;
using System.Collections.Generic;
using System.Text;
using Google.GData.Client;
using Google.GData.Extensions;
using Microsoft.Office.Interop.Outlook;
using Google.GData.Contacts;
using Exception = System.Exception;
using Sync.Events;

namespace ContactsTester
{
    public enum SaveType
    {
        vCard
    }
    public enum UpdateType
    {
        Outlook,
        Gmail,
        None
    }

    public sealed class SyncContact : SyncEvents
    {
        private string _fullName;
        private string _companyName;
        private string _emailAddress;
        private string _mobileNumber;
        private string _homeNumber;
        private string _workNumber;
        private string _notes;
        private ContactItem _originalOutlookContactItem;
        private ContactEntry _originalGoogleItem;

        public UpdateType SyncType;
        public bool InSync;
        public event BeforeContactCreate OnBeforeContactCreate;
        public event AfterContactUpdate OnAfterContactUpdate;
        public event BeforeContactUpdate OnBeforeContactUpdate;
        public event AfterContactCreate OnAfterContactCreate;
        public event FindContact OnFindContact;

        public SyncContact()
        {
            OnBeforeContactCreate += new BeforeContactCreate(EventHandlers.SyncContact_OnBeforeContactCreate);
            OnAfterContactCreate += new AfterContactCreate(EventHandlers.SyncContact_OnAfterContactCreate);
            OnBeforeContactUpdate += new BeforeContactUpdate(EventHandlers.SyncContact_OnBeforeContactUpdate);
            OnAfterContactUpdate += new AfterContactUpdate(EventHandlers.SyncContact_OnAfterContactUpdate);
            OnFindContact += new FindContact(EventHandlers.SyncContact_OnFindContact);
        }

        #region Public Properties
        public ContactItem OriginalOutlookContactItem
        {
            get { return _originalOutlookContactItem; }
            set { _originalOutlookContactItem = value; }
        }

        public ContactEntry OriginalGoogleItem
        {
            get { return _originalGoogleItem; }
            set { _originalGoogleItem = value; }
        }

        public string FullName
        {
            get { return _fullName; }
            set { _fullName = value; }
        }

        public string Company
        {
            get { return _companyName; }
            set { _companyName = value; }
        }

        public string EmailAddress
        {
            get { return _emailAddress; }
            set { _emailAddress = value; }
        }

        public string MobileNumber
        {
            get { return _mobileNumber; }
            set { _mobileNumber = value; }
        }

        public string HomeNumber
        {
            get { return _homeNumber; }
            set { _homeNumber = value; }
        }

        public string WorkNumber
        {
            get { return _workNumber; }
            set { _workNumber = value; }
        }

        public string Notes
        {
            get { return _notes; }
            set { _notes = value; }
        }
        #endregion

        public void InsertMissingContact(ContactsService currentUserService, ApplicationClass currentApplicationInstance, ContactsFeed currentGoogleContacts, UpdateType SyncDirection)
        {
            OnBeforeContactCreate(new Sync.Events.ContactEventArgs(this, SyncDirection));
            switch (SyncDirection)
            {
                case UpdateType.Outlook:
                    InsertOutlookContact();
                    break;
                case UpdateType.Gmail:
                    InsertGoogleContact(currentUserService);
                    break;
                default:
                    throw new ArgumentOutOfRangeException();
            }
            OnAfterContactCreate(new Sync.Events.ContactEventArgs(this, SyncDirection));
        }



        public void UpdateContactDetails(ContactsService currentContactService, UpdateType UpdateDirection)
        {
            OnBeforeContactUpdate(new Sync.Events.ContactEventArgs(this, UpdateDirection));
            switch (UpdateDirection)
            {
                case UpdateType.Outlook:
                    UpdateOutlookContactDetails();
                    break;
                case UpdateType.Gmail:
                    UpdateGoogleContactDetails(currentContactService);
                    break;
                default:
                    throw new ArgumentOutOfRangeException("UpdateDirection");
            }
            OnAfterContactUpdate(new Sync.Events.ContactEventArgs(this, UpdateDirection));
        }

        #region Google Methods

        /// <summary>
        /// Creates a new google contact using the currentContacts Service as provided by the google connector class.
        /// </summary>
        /// <param name="currentUserService"></param>
        private void InsertGoogleContact(ContactsService currentUserService)
        {
            ContactEntry newContact = new ContactEntry();
            newContact.Title = new AtomTextConstruct(AtomTextConstructElementType.Title, FullName);
            CheckAndUpdateNulls();
            if (EmailAddress.Contains("@"))
            {
                newContact.Emails.Add(new EMail(EmailAddress, ContactsRelationships.IsWork));
            }
            AddContactNumbersToContact(newContact);

            Uri feedUri = new Uri(ContactsQuery.CreateContactsUri(currentUserService.Credentials.Username));

            try
            {
                AtomEntry insertedItem = (ContactEntry)currentUserService.Insert(feedUri, newContact);
            }
            catch (Exception exp)
            {
                //HACK: This just ignores the already exists error , for some reason this should not happen but it does.
                //There is a problem with the checking of the existing contacts , this code and the code to check for existing users needs to be
                //reviewed.
                if (!exp.InnerException.Message.Contains("409"))
                {
                    //Push the existing error up to the user if a unexpected error has occured.
                    throw exp;
                }
            }

        }

        /// <summary>
        /// Helper method to add specific details to the google contact, in this case contact numbers are added.
        /// If more number wish to be synced they should be done from here.
        /// </summary>
        /// <param name="newContact"></param>
        private void AddContactNumbersToContact(ContactEntry newContact)
        {
            if (HomeNumber != "")
            {
                PhoneNumber currentHome = new PhoneNumber(HomeNumber);
                currentHome.Rel = ContactsRelationships.IsHome;
                newContact.Phonenumbers.Add(currentHome);
            }
            if (WorkNumber != "")
            {
                PhoneNumber currentWork = new PhoneNumber(WorkNumber);
                currentWork.Rel = ContactsRelationships.IsWork;
                newContact.Phonenumbers.Add(currentWork);
            }
            if (MobileNumber != "")
            {
                PhoneNumber currentMobile = new PhoneNumber(MobileNumber);
                currentMobile.Rel = ContactsRelationships.IsMobile;
                newContact.Phonenumbers.Add(currentMobile);
            }
        }

        /// <summary>
        /// Updates the google contact using the current Contacts services as provided by the google connector class.
        /// </summary>
        /// <param name="currentUserService"></param>
        private void UpdateGoogleContactDetails(ContactsService currentUserService)
        {
            OriginalGoogleItem.Title = new AtomTextConstruct(AtomTextConstructElementType.Title, FullName);
            CheckAndUpdateNulls();
            OriginalGoogleItem.Emails.Add(new EMail(EmailAddress, ContactsRelationships.IsWork));
            AddContactNumbersToContact(OriginalGoogleItem);

            currentUserService.Update(OriginalGoogleItem);
        }

        /// <summary>
        /// Creates a new google contact group, requires the current service as provided by the google connector class 
        /// as well as the new groups name
        /// </summary>
        /// <param name="currentUserService"></param>
        /// <param name="GroupName"></param>
        /// <returns></returns>
        private static GroupEntry CreateGoogleContactGroup(ContactsService currentUserService, string GroupName)
        {
            GroupEntry newGroup = new GroupEntry();
            newGroup.Title = new AtomTextConstruct(AtomTextConstructElementType.Title, GroupName);
            Uri feedUri = new Uri(GroupsQuery.CreateGroupsUri(currentUserService.Credentials.Username));

            GroupEntry insertedGRoup = (GroupEntry)currentUserService.Insert(feedUri, newGroup);
            return insertedGRoup;
        }

        public static bool SyncGroupExists(ContactsService currentService, string GroupName, out GroupEntry updateableGroup)
        {
            GroupsFeed gFeed = new GroupsFeed(currentService.Credentials.Username, new Service("ContactTestApp"));
            GroupsQuery gQuery = new GroupsQuery(GroupsQuery.CreateGroupsUri(currentService.Credentials.Username, GroupsQuery.fullProjection));
            gQuery.ShowDeleted = true;
            gFeed = (GroupsFeed)currentService.Query(gQuery);

            foreach (GroupEntry entry in gFeed.Entries)
            {
                if (entry.Title.Text.ToLower().Trim() == GroupName.ToLower().Trim())
                {
                    updateableGroup = entry;
                    return true;
                }
            }
            updateableGroup = new GroupEntry();
            return false;

        }

        public ContactEntry FindGoogleContactByFullName(ContactsFeed currentContats, string ContactName)
        {
            OnFindContact(new Sync.Events.ContactEventArgs(this, UpdateType.Gmail));
            foreach (ContactEntry contact in currentContats.Entries)
            {
                if (contact.Title.Text == ContactName)
                {
                    return contact;
                }
            }
            return null;
        }

        #endregion

        #region Outlook Methods

        /// <summary>
        /// Creates a new outlook contact by retrieving the default contacts folder and getting a 
        /// reference to the new contact from there.
        /// </summary>
        private void InsertOutlookContact()
        {
            if (FullName == null)
            {
                return;
            }
            if (FullName == "")
            {
                return;
            }
            ApplicationClass newap = new ApplicationClass();

            MAPIFolder additionFolder = GetWorkingFolder(newap);
            CheckAndUpdateNulls();
            ContactItem newContact = (ContactItem)additionFolder.Items.Add(OlItemType.olContactItem);
            CreateOutlookContact(newContact);
        }

        /// <summary>
        /// Gets the current outlook working folder for the requestor
        /// </summary>
        /// <param name="newap"></param>
        /// <returns></returns>
        private static MAPIFolder GetWorkingFolder(ApplicationClass newap)
        {
            return CreateOutlookFolder(newap, "SyncContacts");
        }

        /// <summary>
        /// Creates a new outlook contact ,using the contactItem object provided.
        /// </summary>
        /// <param name="newOutlookContact"></param>
        private void CreateOutlookContact(ContactItem newOutlookContact)
        {
            if (newOutlookContact == null) throw new ArgumentNullException("newOutlookContact");
            newOutlookContact.Title = FullName;
            newOutlookContact.CompanyName = Company;
            newOutlookContact.Email1Address = EmailAddress;
            newOutlookContact.MobileTelephoneNumber = MobileNumber;
            newOutlookContact.HomeTelephoneNumber = HomeNumber;
            newOutlookContact.BusinessTelephoneNumber = WorkNumber;
            newOutlookContact.Save();
        }

        /// <summary>
        /// Find a contact by full name and returns the contactItem to the caller
        /// </summary>
        /// <param name="currentOutlook">The current instantse of the outlook application</param>
        /// <param name="SearchDetails">The details the will be used in the search i.e. the fullname </param>
        /// <returns>The contact item from the Contact list located in the current instance of outlook.</returns>
        private ContactItem FindOutlookContactByFullName(ApplicationClass currentOutlook, string SearchDetails)
        {
            OnFindContact(new Sync.Events.ContactEventArgs(this, UpdateType.Outlook));
            MAPIFolder currentWorkingFolder = GetWorkingFolder(currentOutlook);
            foreach (ContactItem contact in currentWorkingFolder.Items)
            {
                if (contact.FullName == SearchDetails)
                {
                    return contact;
                }
            }
            return null;
        }

        /// <summary>
        /// Updates the outlook contact in outlook by using the current instance details 
        /// </summary>
        private void UpdateOutlookContactDetails()
        {
            CreateOutlookContact(OriginalOutlookContactItem);
        }

        /// <summary>
        /// Creates a new contact folder to hold the new sync contacts , if the contact folder exists it will be returned.
        /// </summary>
        /// <param name="outlookApp"></param>
        /// <param name="FolderName"></param>
        /// <returns></returns>
        private static MAPIFolder CreateOutlookFolder(_Application outlookApp, string FolderName)
        {
            MAPIFolder cntctFolder = outlookApp.Session.GetDefaultFolder(OlDefaultFolders.olFolderContacts);

            MAPIFolder CustomFolder = outlookApp.Session.GetDefaultFolder(OlDefaultFolders.olFolderContacts);

            foreach (MAPIFolder subFolder in cntctFolder.Folders)
            {
                if (subFolder.Name == FolderName)
                {
                    CustomFolder = subFolder;
                }
            }

            //if  custom folder does not exist create a new folder with name  custom folder name.
            if (CustomFolder == null)
            {
                CustomFolder = cntctFolder.Folders.Add(FolderName, OlDefaultFolders.olFolderContacts);
            }
            return CustomFolder;
        }
        #endregion

        private void CheckAndUpdateNulls()
        {
            if (FullName == null)
                FullName = "";
            if (Company == null)
                Company = "";
            if (EmailAddress == null)
                EmailAddress = "";
            if (MobileNumber == null)
                MobileNumber = "";
            if (HomeNumber == null)
                HomeNumber = "";
            if (WorkNumber == null)
                WorkNumber = "";
            if (Notes == null)
                Notes = "";
        }

        /// <summary>
        /// Check if the contact exists in the specified array
        /// </summary>
        /// <param name="ContactName">Contact name to search for</param>
        /// <param name="contactsToSearch">List of contacts to search through</param>
        /// <returns>Wether the contact exists or not.</returns>
        public static bool Exists(string ContactName, SyncContact[] contactsToSearch)
        {

            foreach (SyncContact contact in contactsToSearch)
            {
                if (contact.FullName == null)
                {
                    Sync.Events.ContactEventArgs args = new Sync.Events.ContactEventArgs(contact, UpdateType.None);
                    args.Exists = false;
                    // TODO : Move the delegate/events to this class / Sync Contacts
                    //OnContactExists(args);
                    return false;
                }
                if (contact.FullName.ToLower() == ContactName.ToLower())
                {
                    Sync.Events.ContactEventArgs args = new Sync.Events.ContactEventArgs(contact, UpdateType.None);
                    args.Exists = true;
                    // TODO : Move the delegate/events to this class / Sync Contacts
                    //OnContactExists(args);
                    return true;
                }
            }
            return false;
        }
    }
}
